Creating Geometric Shapes
This programming recipe shows you how to use QuickDraw GX to create a small graphics application that allows the user to create path shapes with a series of four mouse clicks, as shown in Figure 6-1.Figure 6-1 Building a path shape
The user builds a path shape by clicking the mouse in the application's window. Each time the user clicks the mouse, the sample application draws
a small black square representing a geometric point. These small black
squares are called geometry control handles. Later recipes in this chapter show how you can allow the user to edit the path shape using these handles.
When the user has clicked the mouse four times, the sample application creates a path shape using the four specified points as geometric points. For the purposes of this application, the second and third are interpreted as off-
- Note
- There is an important distinction between geometric points, introduced in Chapter 2, and geometry control handles, introduced in this chapter. Geometric points are coordinates stored in shape geometries. They are used to define shapes. Geometry control handles are small square shapes used to represent geometric points to your application's users.<8bat>u
![]()
curve control points.Once the user completes a path shape, they can start creating a new path shape by clicking the mouse again. At this point, the four geometry control handles from the previous path disappear, a new geometry control handle appears, and the path-creating process starts again.
Overview of Recipe Steps
The steps in this recipe show you how to:
You need to follow the instructions in each step of this recipe to implement the path-creating program.
- Declare and initialize a document structure
- Obtain mouse location
- Create user-editable path
- Deselect the previous path shape
- Create a new path shape
- Create and draw a geometry control handle
- Add a new geometry point to the path shape
- Draw the path shape and add it to the window picture
- Respond to update events
- Dispose of information in the document structure
Functions Used in This Recipe
QuickDraw GX functions used in this recipe:
GXNewWindowViewPort
"QuickDraw GX and the
Macintosh Environment"
QuickDraw GX Environment and UtilitiesGXNewPicture
"Picture Shapes"
QuickDraw GX GraphicsGXNewShape
"Shape Objects"
QuickDraw GX ObjectsGXSetShapeFill
"Shape Objects"
QuickDraw GX ObjectsGXNewRectangle
"Geometric Shapes"
QuickDraw GX GraphicsGXDrawShape
"Shape Objects"
QuickDraw GX ObjectsGXSetPathParts
"Geometric Shapes"
QuickDraw GX GraphicsGXSetPictureParts
"Picture Shapes"
QuickDraw GX GraphicsGXDisposeShape
"Shape Objects"
QuickDraw GX ObjectsGXDisposeViewPort
"View-Related Objects"
QuickDraw GX ObjectsStandard Macintosh functions used in this recipe:
GetNewWindow
"Window Manager"
Macintosh Toolbox EssentialsShowWindow
"Window Manager"
Macintosh Toolbox EssentialsWaitNextEvent
"Event Manager"
Macintosh Toolbox EssentialsFrontWindow
"Window Manager"
Macintosh Toolbox EssentialsSelectWindow
"Window Manager"
Macintosh Toolbox EssentialsDisposeWindow
"Window Manager"
Macintosh Toolbox EssentialsNewPtr
"Memory Manager"
MemoryGlobalToLocal
"Basic QuickDraw"
Imaging With QuickDrawThis recipe gives a brief description of these functions; you can find complete reference information for these functions in the Inside Macintosh suite of books.
This recipe also uses functions from the QuickDraw GX libraries:
SetDefaultViewPort
transform library SetShapeFastXorTransfer
transferMode library Recipe Step Descriptions
In this section, each step is described individually.
- Declare and initialize a document structure
Macintosh applications frequently store document-related information in a document information structure referenced by the window record that represents the document's window. (Here the term document refers to the group of path shapes drawn in the window.) This recipe uses a structure that contains this information about the document:
- the number of geometric points the user has specified so far in the path shape being created
- a reference to the path shape being created
- an array of references to the small rectangle shapes used to represent the geometric points that the user is creating
- a reference to a picture containing all the path shapes that the user has created so far
Here is the data type you can use to define this document information structure:
#define kNumOfControls 4
typedef struct {
WindowPtr window;
gxViewPort viewPort;
long pointCount;
gxShape selectedPath;
gxShape controls[kNumOfControls];
gxShape picture;
} DocumentInfo, *DocumentPtr;Note that for the purpose of this recipe, a path is allowed to have only four control points; thus the fourth click indicates the shape is complete.
You also need to create a global variable that points to the document information structure for the current document:
DocumentPtr gCurrent;In your initialization function, you need to create a window, create a view port and attach it to the window, and initialize the other fields of the document information structure, as with this code example:
Ptr document;
WindowPtr window;
int count;
document = NewPtr(sizeof(DocumentInfo));
if (document != nil) {
window = (WindowPtr) GetNewCWindow(rDocWindow, storage,
(WindowPtr) -1);
((WindowPeek) window)->refCon = (long) document;
document->window = window
document->viewPort = GXNewWindowViewPort(window);
SetDefaultViewPort(document->viewPort);
document->pointCount = 0;
document->selectedPath = nil;
for (count = 0; count < kNumOfControls; count++)
document->controls[count] = nil;
document->picture = GXNewShape(gxPictureType);
gCurrent = document; /* store in global variable */
ShowWindow(window);
}Note that, for simplicity, this recipe uses a window with a single view port (and therefore no scrolling).
After the code creates the window, it performs the following initializations on the document information structure:
- The point count is initialized to 0, reflecting the fact that the user hasn't clicked the mouse to create any geometric points yet.
- The selected path reference is initialized to
nil
, indicating that no path is currently being created.- The four references to the control handle shapes are also initialized to
nil
, indicating that no geometric handles are currently being displayed.- The picture shape is created with a geometry containing no shapes.
- Obtain mouse location
When the user presses the mouse, your application receives information about the event from the standard Macintosh function
WaitNextEvent
. You can determine the type of event by examining thewhat
field of the event record returned by this function. If the event is a mouse-down event, you examine thewhere
field of the event record to determine where the mouse was clicked. If the mouse was clicked in the content part of a window, you use theFrontWindow
standard Macintosh function to determine if it was the frontmost window.If the mouse was not clicked in the frontmost window, you can use the standard Macintosh function
SelectWindow
to select the window. At this time, you would probably want to update yourgCurrent
global variable so that it references the document information for the new front window.If the mouse was clicked in the frontmost window, the user is specifying a new geometric point. The
MyContentClick
sample function shown here handles this situation:
void MyContentClick(WindowPtr window, EventRecord *event)
{
Point eventPoint;
gxPoint localPoint;
eventPoint = event->where;
GlobalToLocal(&eventPoint);
localPoint.x = ff(eventPoint.h);
localPoint.y = ff(eventPoint.v);
MyHandleCreatePath(&localPoint); /* See Step 3. */
}The
MyContentClick
function translates the mouse location from QuickDraw global coordinates to QuickDraw local coordinates, which are the QuickDraw coordinates of the mouse location in the window. Since the window and view port created in Step 1 do not allow for scrolling, it is easy to translate these coordinates into QuickDraw GX local coordinates--all you have to do is convert the integer coordinates of QuickDraw to fixed-point coordinates for QuickDraw GX.- Create user-editable path
After translating the coordinates, the
MyContentClick
function calls theMyHandleCreatePath
sample function to respond to the user's mouse click.The flow of control for the
MyHandleCreatePath
function is shown here:
void MyHandleCreatePath(gxPoint *hitPoint)
{
/* Declare local variables -- see Step 7. */
if (gCurrent->pointCount == 0) {
/* Deselect previous path -- see Step 4 */
/* Create new path shape -- see Step 5. */
}
/* Create & draw a control handle -- see Step 6. */
gCurrent->pointCount++;
/* Add new geometry point to path shape -- see Step 7. */
if (gCurrent->pointCount == kNumOfControls) {
/* Draw the path and add to picture -- see Step 8. */
gCurrent->pointCount = 0;
}
}This function examines the
pointCount
field of the document information structure to determine how many geometric points the user has specified for the path shape currently being created. If no points have been specified, this is the first point of a new path, so the function deselects the previously selected path and creates a new path shape to represent the new path the user is creating.No matter what the point count, the function then draws a geometry control handle where the use clicked the mouse, increments the point count, and adds the new geometric point to the path shape currently being created.
Finally, the function examines the point count to determine if this is the last point (the fourth point) in the path shape. If so, the function draws the new path shape and adds it to the document picture.
The next few steps--Steps 4 through 8--show how to implement the parts of the
MyHandleCreatePath
function.- Deselect the previous path shape
The first thing your
MyHandleCreatePath
function (defined in the previous step) does is determine if the user is starting to create a new path shape. If so, you need to determine if there is a previously created path shape. If so, its geometry control handles are still being displayed, so you need to erase them and dispose of the shapes that represent them:
if (gCurrent->selectedPath != nil) {
My
ErasePathControlHandles();
MyDisposePathControlHandles();
GXDisposeShape(gCurrent->selectedPath);
}The
MyDisposePathControlHandles
sample function is shown here:
void MyDisposePathControlHandles()
{
int index;
for (index = 0; index < kNumOfControls; index++)
if (gCurrent->controls[index] != nil)
GXDisposeShape(gCurrent->controls[index]);
}This function disposes of each of the shapes referenced by the
controls
array in the document information structure.- Create a new path shape
The first time the user presses the mouse button, you need to create a new path shape. (You also need to create a new path shape when the user presses the mouse button and the previously created shape is already finished.)
This new path shape has no geometric points defined yet, so you need to use the
GXNewShape
function, thus:
gCurrent->selectedPath = GXNewShape(gxPathType);A reference to the new shape is stored in the
selectedPath
field of the current document information structure. You can set the shape fill property for this new path using theGXSetShapeFill
function:
GXSetShapeFill(gCurrent->selectedPath, gxFrameFill);You need to set the shape fill for the new path because the default shape fill for path shapes is a solid shape fill, which would make the path be drawn as a solid area, rather than a contour, when you draw it in Step 8.
- Create and draw a geometry control handle
Whenever the user clicks the mouse, you need to draw a geometry control handle at the specified location. The
MyCreateControlHandle
sample function shows how you can do this:
void MyCreateControlHandle(gxShape *theControl,
gxPoint *where) {
gxRectangle smallSquare;
smallSquare.left = where->x - ff(2);
smallSquare.top = where->y - ff(2);
smallSquare.right = where->x + ff(2);
smallSquare.bottom = where->y + ff(2);
*theControl = GXNewRectangle(&smallSquare);
SetShapeFastXorTransfer(*theControl, &gBlack, &gWhite);
}This function takes two parameters. The second parameter specifies where the geometry control handle should be located. Using this location, the function creates a small square shape to represent the geometry control handle and returns a reference to the new shape in its first parameter.
The
MyCreateControlHandle
function sets the transfer mode of the new shape using theSetShapeFastXorTransfer
QuickDraw GX library function. This function sets the transfer mode of the shape to exclusive-OR so that when you draw the shape (in black and white in this example) it inverts the pixels it's drawn over. This way, you can erase the shape simply by drawing it a second time.
- Note
- The transfer mode used by the library function
SetShapeFastXorTransfer
has certain limitations--
for example, it does not work when a single shape is drawn across multiple monitors. <8bat>uTo set this transfer mode, you need to specify the foreground and background colors, which you can do using these global variables:
gxColor gWhite;
gxColor gBlack;
gBlack.space = gxGraySpace;
gBlack.element.gray = 0;
gBlack.profile = nil;
gWhite.space = gxGraySpace;
gWhite.element.gray = 0xFFFF;
gWhite.profile = nil;In your
MyHandleCreatePath
function (defined in Step 3), you should call theMyCreateControlHandle
function using this line of code:
MyCreateControlHandle(&gCurrent->controls[gCurrent->pointCount]
hitPoint);This function call specifies the hit point as the location of the new geometry control handle, and also specifies that the shape representing the geometry control handle should be stored in the document information structure.
You can then draw the new geometry control handle:
GXDrawShape(gCurrent->controls[gCurrent->pointCount]);Since you set the transfer mode of the control handle to be exclusive-OR, it inverts anything it is drawn over. For example, if the user presses the mouse over a previously created path shape, the pixels of the previous path shape show through the black control handle as white.
- Add a new geometry point to the path shape
Now that you've drawn the geometry control handle, you need to add a corresponding geometric point to the path shape being built. You can add geometric points to path shapes using the
GXSetPathParts
function. To use this function, however, you need to store the new point in a multiple-path structure. This structure allows you to specify whether the point is on curve or off curve. You can define and initialize a multiple-path structure using this definition:
long initialValues[] = {1, /* just one contour */
1, /* just one point */
0x00000000, /* on curve */
ff(0), ff(0)}; /* initial position */This array contains five values: the first indicates that the path being defined has only one contour, the second indicates that the contour has only one point, the third contains bit flags indicating that the point is an on-curve point, and the fourth and fifth specify the initial coordinates of the point. Before you insert this new point into the path shape, you need to specify the actual coordinates of the point:
initialValues[3] = hitPoint->x;
initialValues[4] = hitPoint->y;You also need to edit the control bits to specify whether the point being added is an on-curve or an off-curve point:
if ((gCurrent->pointCount == 2) || (gCurrent->pointCount == 3))
initialValues[2] = 0x80000000; /* off curve */
else
initialValues[2] = 0x00000000; /* on curve */Then, to turn the array into a multiple-path structure, you can use these statements:
gxPaths *insertedPoint;
insertedPoint = (gxPaths *) initialValues;Now you have a structure you can use to insert a single point into your path shape. Before you insert the new point, however, you need to edit the fields of this structure so that they specify the correct position.
You also need to insert the new point into the path using the
GXSetPathParts
function:
GXSetPathParts(gCurrent->selectedPath, /* path to edit */
0, 0, /* insert at end */
insertedPoint, /* path to insert */
gxBreakNeitherEdit); /* connect to contour */The first parameter to this function specifies the path shape. The second and third parameters specify where to insert the new point and how many old points to replace. Specifying 0 for both of these parameters indicates that you want to insert the new point at the end of the existing geometry.
The fourth parameter specifies the information to be added to the geometry--
in this case, the single new geometry point. The final parameter specifies
how the point should be added. The constantgxBreakNeitherEdit
specifies that it should be appended to the existing contour.- Draw the path shape and add it to the window picture
In your
MyHandleCreatePath
function (defined in Step 3), you determine if the mouse click is the specifying a fourth, and final, geometry point for the path. If so, the path shape is done and ready to be added to the window picture. You can use theGXSetPictureParts
function to add the new path shape to the window:
GXSetPictureParts(gCurrent->picture,
0, 0, 1, /* insert one shape at end */
&gCurrent->selectedPath,
nil, /* no overriding styles */
nil, /* no overriding inks */
nil); /* no overriding transforms */To draw the completed path shape properly, you need to erase the existing geometry control handles, draw the path, and then draw them again,
like this:
MyErasePathControlHandles();
GXDrawShape(gCurrent-selectedPath);
MyDrawPathControlHandles();This is because the geometry control handles have an exclusive-OR transfer mode. This function does the actual drawing of the geometry control handles:
void MyDrawPathControlHandles()
{
int index;
for (index = 0; index < kNumOfControls; index++)
if (gCurrent->controls[index] != nil)
GXDrawShape(gCurrent->controls[index]);
}Thus, the code to erase them actually draws them:
void MyErasePathControlHandles()
{
MyDrawPathControlHandles(); /* XOR drawing = erasing */
}- Respond to update events
When your application receives an update event, you need to redraw the contents of the window. You can do this using the
MyDrawWindow
sample function:
void MyDrawWindow()
{
GXDrawShape(gCurrent->picture);
if (gCurrent->selectedPath != nil)
MyDrawPathControlHandles();
}This simple function redraws the contents of the entire window: the picture associated with the window and, if there is a currently selected path shape, the geometry control handles for that shape. Before you call this function, you need to call the standard Macintosh function
BeginUpdate
, and after you call this function, you need to call the standard Macintosh functionEndUpdate
.- Dispose of information in the document structure
When the user of your application closes a window, you need to dispose of the shapes associated with that window (after you give the user a chance to save them, of course). This code disposes of all the shapes referenced by the current document information structure:
if (gCurrent->selectedPath != nil)
GXDisposeShape(gCurrent->selectedPath);
MyDisposePathControlHandles();
GXDisposeShape(gCurrent->picture);
GXDisposeViewPort(gCurrent->viewPort);You also need to dispose of your window using standard Macintosh function
DisposeWindow
.
Related Recipes
The following three recipes show how to add more functionality to the path-
editing program:
The last recipe in this chapter, "Dragging Shapes Using Offscreen Bitmaps," on page 217, shows how to allow the user to drag colored circles around a window. This recipe uses an offscreen bitmap to smooth the redrawing used for dragging feedback.
- "Selecting Shapes," described next, shows how to hit-test the window picture so that you can allow users to select a previously created path.
- "Editing Shape Geometries," beginning on page 199, shows how to allow users to redefine a previously created path by dragging on a geometry control handle.
- "Editing Shape Transforms," beginning on page 206, shows how to provide a different kind of control handle--a transform control handle. These control handles allow the user to edit the transform of the shape, rather than the geometry of the shape.
The recipes in Chapter 4, "Using the QuickDraw GX Environment," show you how to initialize QuickDraw GX and set up the QuickDraw GX debugging facilities. You should read the recipes in that chapter before using any recipes in this chapter.
The recipes in Chapter 5, "Using Macintosh Windows," show you how to create Macintosh windows, attach QuickDraw GX view ports to them, and implement zooming, resizing, and scrolling. You need to be familiar with the information in that chapter before you can display QuickDraw GX graphics in a Macintosh window.
The recipes in Chapter 7, "Handling Typography," show you how to create and manipulate typographic, rather than graphics, shapes.
The recipes in Chapter 8, "Printing," show you how to send your graphics and typographic shapes to a printer.